import * as cc from 'cc'
import { SpriteFrame, __private, Node, game, Game, Texture2D } from 'cc'
import { DEBUG } from 'cc/env'
import { MultiTexture2d } from './MultiTexture2d'

export enum Stage {
    // Stencil disabled
    DISABLED = 0,
    // Clear stencil buffer
    CLEAR = 1,
    // Entering a new level, should handle new stencil
    ENTER_LEVEL = 2,
    // In content
    ENABLED = 3,
    // Exiting a level, should restore old stencil or disable
    EXIT_LEVEL = 4,
    // Clear stencil buffer & USE INVERTED
    CLEAR_INVERTED = 5,
    // Entering a new level & USE INVERTED
    ENTER_LEVEL_INVERTED = 6,
}

//最大纹理合批数量
const MaxTextureCount = 8

export type frameType = __private._cocos_asset_assets_texture_base__TextureBase | SpriteFrame | null

export class UIBatchManager {
    private static instance: UIBatchManager = null
    textureList: Array<cc.gfx.Texture>
    batchEnable: boolean = true

    _texture: Texture2D = new Texture2D()
    set texture(frame: frameType) {
        this._texture["_gfxSampler"] = frame!.getGFXSampler()
        this._texture["_gfxTextureView"] = frame!.getGFXTexture()
    }

    get texture() {
        return this._texture
    }
    static GetInstance(): UIBatchManager {
        if (UIBatchManager.instance == null) {
            UIBatchManager.instance = new UIBatchManager()
        }
        return this.instance
    }

    constructor() {
        this.textureList = new Array<cc.gfx.Texture>()
    }

    reset() {
        this.textureList.length = 0
    }
}
//@ts-ignore
cc.UI.prototype.currMaterial = null
Object.defineProperty(cc.UI.prototype, "_currMaterial", {
    set: function (material) {
        if (this.currMaterial !== material) {
            this.currMaterial = material
            UIBatchManager.GetInstance().reset()
        }
    },
    get: function () {
        return this.currMaterial
    },
    enumerable: true,
    configurable: true
})

game.once(Game.EVENT_ENGINE_INITED, function () {
    cc.director.on(cc.Director.EVENT_AFTER_DRAW, function () {
        UIBatchManager.GetInstance().reset()
    })

    //@ts-ignore
    cc.UI.prototype["_currRDHash"] = -0x1
})

let dataHash = 0
let material: cc.Material = null
let bufferId = -1
let chunkBufferId = -1
let textureIndex = -1
let gfxTexture: cc.gfx.Texture = null
let needsNewTexture = false
let needsUpdate = false
let multiTextureEnable = false

cc.UI.prototype.commitComp = function (comp: cc.UIRenderer, renderData: cc.BaseRenderData | null, frame: frameType, assembler, transform: Node | null): void {
    dataHash = 0
    material = null
    bufferId = -1
    chunkBufferId = -1
    if (renderData?.chunk) {
        if (!renderData.isValid()) return
        dataHash = renderData.dataHash
        material = renderData.material
        chunkBufferId = renderData.chunk.bufferId
        bufferId = (chunkBufferId + 1) * (renderData["layer"] + 1)
    }
    // Notice: A little hack, if it is for mask, not need update here, while control by stencilManger
    if (comp.stencilStage === 2 || comp.stencilStage === 6) {
        this._insertMaskBatch(comp)
    } else {
        comp.stencilStage = cc.StencilManager.sharedManager!.stage
    }
    const depthStencilStateStage = comp.stencilStage
    textureIndex = -1
    gfxTexture = null
    needsNewTexture = false
    needsUpdate = false
    multiTextureEnable = comp instanceof MultiTexture2d ? true : false

    if (material && multiTextureEnable) {
        if (UIBatchManager.GetInstance().batchEnable) {
            gfxTexture = frame?.getGFXTexture()
            needsUpdate = true
            textureIndex = UIBatchManager.GetInstance().textureList.indexOf(gfxTexture)
            if (textureIndex < 0 && UIBatchManager.GetInstance().textureList.length >= MaxTextureCount) {
                needsNewTexture = true
            }
            if (this._currMaterial) {
                material = this._currMaterial
                dataHash = this._currHash
                if (this._currRDHash !== bufferId) {
                    needsNewTexture = true
                    textureIndex = -1
                }
            }
        }
        else {
            textureIndex = 0
            needsUpdate = true
        }
    }


    if (needsNewTexture || this._currHash !== dataHash || dataHash === 0 || this._currMaterial !== material
        || this._currDepthStencilStateStage !== depthStencilStateStage) {
        // Merge all previous data to a render batch, and update buffer for next render data
        this.autoMergeBatches(this._currComponent!)
        if (renderData && !renderData._isMeshBuffer) {
            this.updateBuffer(renderData.vertexFormat, chunkBufferId)
        }
        this._currRDHash = bufferId
        this._currRenderData = renderData
        this._currHash = renderData ? renderData.dataHash : 0
        this._currComponent = comp
        this._currTransform = transform
        this._currDepthStencilStateStage = depthStencilStateStage
        this._currLayer = comp.node.layer

        if (!needsNewTexture) {
            this._currMaterial = comp.getRenderMaterial(0)!
        }
        else {
            this._currMaterial = comp.getMaterialInstance(0)!
        }
        material != this._currMaterial ? UIBatchManager.GetInstance().reset() : null

        if (frame) {
            if (DEBUG) {
                cc.assert(frame.isValid, 'frame should not be invalid, it may have been released')
            }
            this._currTexture = frame.getGFXTexture()
            this._currSampler = frame.getGFXSampler()
            this._currTextureHash = frame.getHash()
            this._currSamplerHash = this._currSampler.hash
        } else {
            this._currTexture = null
            this._currSampler = null
            this._currTextureHash = 0
            this._currSamplerHash = 0
        }
    }
    assembler.fillBuffers(comp, this)

    if (needsUpdate) {
        if (textureIndex < 0) {
            textureIndex = UIBatchManager.GetInstance().textureList.length
            UIBatchManager.GetInstance().textureList.push(gfxTexture)
            if (textureIndex > 0) {
                UIBatchManager.GetInstance().texture = frame
                const texturePropertyName = 'texture' + textureIndex
                this._currMaterial.setProperty(texturePropertyName, UIBatchManager.GetInstance().texture)
            }
        }
        //设定值
        if (comp instanceof MultiTexture2d) {
            comp.textureIdx = textureIndex
            assembler.updateTextureIdx(comp)
        }

    }



}